/* This module implements a simple option menu. The options of the menu are
 * created as a array of OptionMenu, where the fields are:
 *
 *   - text: the option text;
 *   - action: the action to be taken when the option is chosen;
 *   - data: helper data to the action.
 *
 *  The possible values for action/data are:
 *
 *    - SUBMENU: activates a submenu, where data is the pointer for the new
 *                OptionMenu object;
 *    - FUNCTION_CALL: calls the function pointed by `data`;
 *    - BOOLEAN: a Yes/No option, mapped to a variable pointed by `data`;
 *    - UINTEGER: a positive integer counter, mapped to a variable pointer by
 *                `data`, and `data2` is the max value;
 *    - STRING: a text input, mapped to a variable pointer by `data`, `data2`
 *              contains the max size of the string;
 *    - DO_NOTHING: the name says it all;
 *    - BACK: return to the previous menu;
 *    - NOT_IMPLEMENTED: go to a menu saying the option is not implemented.
 *
 *  The menu is created with `create_menu`, where a menu structure is returned.
 *  This structure contains a field `sf`, that is the menu image, that must
 *  be drawn by the caller.
 *
 *  The caller process function must call `process_menu`, that'll process the
 *  menu and return true if the image was changed.
 *
 *  A example of a menu implementation can be seen in gui/title.c. */

#include "menu.h"

#include <stdlib.h>
#include <string.h>

#include "global.h"
#include "algorithm.h"

// spacing for the menu border
static const int LINE_WIDTH = 4,
	         INNER_SPACE = 10;


// palette colors
static SDL_Color white = {255,255,255},
		 black = {0,0,0};


// "Not implemented" submenu
OptionMenu not_implemented[] = {
	{ gettext_noop("Not implemented!"), DO_NOTHING, NULL },
	{ gettext_noop("Back"), BACK, NULL },
	{ NULL, 0, NULL }
};

// Draw the menu image. The function do its own cleanup.
static void draw_menu(Menu *m)
{
	if(m->sf)
		SDL_FreeSurface(m->sf);

	// create text images
	SDL_Surface* texts[m->n_options*2];
	int i = 0;
	int max_w = 0;
	while(m->current_menu[i/2].text)
	{
		char buf[1024];
		if(strcmp(m->current_menu[i/2].text, "_EXIT_TO_OS") == 0)
		{
			sprintf(buf, _("Exit to %s"), OS);
		}
		else if(m->current_menu[i/2].action == BOOLEAN)
		{
			_Bool* v = m->current_menu[i/2].data;
			sprintf(buf, "%s: %s", _(m->current_menu[i/2].text),
					*v ? _("Yes") : _("No"));
		}
		else if(m->current_menu[i/2].action == UINTEGER)
		{
			int* n = m->current_menu[i/2].data;
			sprintf(buf, "%s: %d", _(m->current_menu[i/2].text), *n);
		}
		else if(m->current_menu[i/2].action == STRING)
		{
			sprintf(buf, "%s: %s", _(m->current_menu[i/2].text),
					(char*)m->current_menu[i/2].data);
		}
		else
			strcpy(buf, _(m->current_menu[i/2].text));
		texts[i] = TTF_RenderUTF8_Solid(m->font,
				buf, m->bw ? black : white);
		texts[i+1] = TTF_RenderUTF8_Solid(m->font,
				buf, m->bw ? white : black);
		if(texts[i]->w > max_w)
			max_w = texts[i]->w;
		i += 2;
	}

	// create menu surface
	SDL_Surface* sf = SDL_CreateRGBSurface(SDL_SWSURFACE,
			max_w + (LINE_WIDTH * 2) + (INNER_SPACE * 2),
			m->n_options * TTF_FontLineSkip(m->font) + (LINE_WIDTH * 2) + (INNER_SPACE * 2),
			8, 0, 0, 0, 0);
	SDL_SetColorKey(sf, SDL_SRCCOLORKEY|SDL_RLEACCEL, TRANSPARENT);
	Uint8* pixels = ((Uint8*)sf->pixels);

	// setup palette
	SDL_Color c[256] = { { 0, 0, 0 }, { 255, 255, 255 }, { 168, 168, 168 } };
	c[CURSOR] = (SDL_Color) { 255, 255, 255 };
	SDL_SetColors(sf, c, 0, 255);

	// draw border
	SDL_Rect r = { 0, 0, sf->w-1, sf->h-1 };
	SDL_FillRect(sf, &r, m->bw ? BLACK : WHITE);
	r.x = r.y = LINE_WIDTH;
	r.w = sf->w - 2*LINE_WIDTH - 1;
	r.h = sf->h - 2*LINE_WIDTH - 1;
	SDL_FillRect(sf, &r, TRANSPARENT);
	r.x = LINE_WIDTH;
	r.y = LINE_WIDTH;
	r.h = 1;
	r.w = sf->w - LINE_WIDTH * 2 - 1;
	SDL_FillRect(sf, &r, m->bw ? WHITE : BLACK);
	r.h = sf->h - LINE_WIDTH * 2 - 1;
	r.w = 1;
	SDL_FillRect(sf, &r, m->bw ? WHITE : BLACK);
	pixels[sf->w - 1] = TRANSPARENT;
	pixels[(sf->h - 1) * sf->pitch] = TRANSPARENT;

	// draw options
	SDL_Rect r1 = { LINE_WIDTH + INNER_SPACE, LINE_WIDTH + INNER_SPACE, 0, 0 },
	         r2 = { r1.x+1, r1.y+1, 0, 0 };
	for(i=0; i<m->n_options*2; i+=2)
	{
		// draw selected option
		if(m->option == i/2)
			SDL_FillRect(sf, &(SDL_Rect) { r1.x-2, r1.y, sf->w - 2 * (LINE_WIDTH+INNER_SPACE) + 4, TTF_FontLineSkip(m->font) }, GREY);
		// draw option text
		SDL_BlitSurface(texts[i+1], NULL, sf, &r2);
		SDL_BlitSurface(texts[i], NULL, sf, &r1);
		// draw cursor
		if(m->editing_text && m->option == i/2)
			SDL_FillRect(sf, &(SDL_Rect) { r1.x + texts[i]->w, r1.y, 2, texts[i]->h }, CURSOR);
		// create rectangle to validate mouse events
		m->current_menu[i/2].r = (SDL_Rect) { r1.x, r1.y, sf->w - 2 * (LINE_WIDTH+INNER_SPACE) + 4, r1.h+1 };

		r1.y += TTF_FontLineSkip(m->font);
		r2.y += TTF_FontLineSkip(m->font);
	}

	// free used surfaces
	for(i=0; i<(m->n_options*2); i++)
		SDL_FreeSurface(texts[i]);

	m->sf = sf;
}


// Create a new menu, where:
//   - om: OptionMenu array (array of menu items);
//   - font: font used to draw menu;
//   - bw: true if black over white, false if white over black
Menu* create_menu(OptionMenu* om, TTF_Font *font, _Bool bw)
{
	Menu* m = malloc(sizeof(Menu));
	m->current_menu = om;
	m->option = 0;
	m->sf = NULL;
	m->font = font;
	m->bw = bw;
	m->editing_text = false;
	m->tmp_editing = NULL;

	// count options
	m->n_options = 0;
	while(m->current_menu[m->n_options].text)
		++m->n_options;

	draw_menu(m);

	return m;
}


// The user chose a option.
static void select_option(Menu* m, int data)
{
	OptionMenu *temp, *om = &m->current_menu[m->option];

	switch(om->action)
	{
		case NOT_IMPLEMENTED:
		case SUBMENU:
		case BACK: // TODO - make a stack for submenu
			temp = m->current_menu;
			if(om->action == NOT_IMPLEMENTED)
			{
				m->current_menu = not_implemented;
				m->option = 1;
			}
			else
			{
				m->current_menu = om->data;
				m->option = 0;
			}
			m->n_options = 0;
			while(m->current_menu[m->n_options].text)
			{
				if(m->current_menu[m->n_options].action == BACK)
					m->current_menu[m->n_options].data = temp;
				++m->n_options;
			}
			break;
		case FUNCTION_CALL:
			if(om->data2 == NULL)
				((void(*)())om->data)(); // call function
			else
				((void(*)(void*))om->data)(om->data2);
			break;
		case BOOLEAN:
			{
				_Bool* v = m->current_menu[m->option].data;
				*v = !*v;
			}
			break;
		case UINTEGER:
			{
				long* v = m->current_menu[m->option].data;
				*v += data;
				if(*v > (long)m->current_menu[m->option].data2)
					*v = (long)m->current_menu[m->option].data2;
				if(*v < 0)
					*v = 0;
			}
			break;
		case STRING:
			m->editing_text = true;
			m->tmp_editing = strdup(om->data);
			SDL_EnableUNICODE(1);
			break;
		case DO_NOTHING:
			break;
	}
}


// Process the events that might cause a change in the menu. If the menu is
// redraw (for example, the option changed), returns true.
// The parameters menu_x and menu_y are the position of the menu on the screen.
_Bool process_menu(Menu* m, SDL_Event* e, int menu_x, int menu_y)
{
	if(m->editing_text)
	{
		char* str = m->current_menu[m->option].data;

		if(e->type == SDL_KEYDOWN && e->key.keysym.unicode)
		{
			int i = strlen(str);

			if(e->key.keysym.unicode < 32)
				switch(e->key.keysym.sym)
				{
					case SDLK_BACKSPACE:
						if(i == 1)
							str[0] = 0;
						else if(i > 0)
						{
							if(str[i-2] & 0xf0)
								str[i-2] = 0;
							else
								str[i-1] = 0;
						}
						draw_menu(m);
						return true;
					case SDLK_ESCAPE:
						strcpy(str, m->tmp_editing);
						/* falltrough */
					case SDLK_KP_ENTER:
					case SDLK_RETURN:
						if(m->tmp_editing)
							free(m->tmp_editing);
						m->tmp_editing = NULL;
						SDL_EnableUNICODE(0);
						m->editing_text = false;
						draw_menu(m);
						return true;
					default:
						return false;
				}

			if(i >= (long)m->current_menu[m->option].data2 - 1)
				return false;

			if(e->key.keysym.unicode < 0x80)
			{
				str[i] = e->key.keysym.unicode;
				str[i+1] = 0;
			}
			else if(e->key.keysym.unicode < 0x800)
			{
				str[i] = (0xc0 | e->key.keysym.unicode >> 6);
				str[i+1] = ((0x80 | e->key.keysym.unicode) & 0x3f);
				str[i+2] = 0;
			}
			else if(e->key.keysym.unicode < 0x800)
			{
				str[i] =   ((0xe0 | e->key.keysym.unicode) >> 12);
				str[i+1] = ((0x80 | e->key.keysym.unicode) >> 6 & 0x3f);
				str[i+2] = ((0x80 | e->key.keysym.unicode) & 0x3f);
				str[i+3] = 0;
			}
			draw_menu(m);
			return true;
		}
		return false;
	}

	if(e->type == SDL_KEYDOWN)
	{
		switch(e->key.keysym.sym)
		{
			case SDLK_UP:
				--m->option;
				if(m->option == -1)
					m->option = m->n_options-1;
				draw_menu(m);
				return true;
			case SDLK_DOWN:
				++m->option;
				if(m->option == m->n_options)
					m->option = 0;
				draw_menu(m);
				return true;
			case SDLK_SPACE:
			case SDLK_RETURN:
			case SDLK_KP_ENTER:
				select_option(m, 0);
				draw_menu(m);
				return true;
			case SDLK_RIGHT:
				switch(m->current_menu[m->option].action)
				{
					case SUBMENU:
					case BOOLEAN:
						select_option(m, 0);
						draw_menu(m);
						return true;
					case UINTEGER:
						select_option(m, +1);
						draw_menu(m);
						return true;
					default:
						return false;
				}
				break;
			case SDLK_LEFT:
				switch(m->current_menu[m->option].action)
				{
					case BACK:
						select_option(m, 0);
						draw_menu(m);
						return true;
					case UINTEGER:
						select_option(m, -1);
						draw_menu(m);
						return true;
					default:
						return false;
				}
				break;
			default:
				return false;
		}
	}
	else if(e->type == SDL_MOUSEMOTION)
	{
		int i;
		for(i=0; i < m->n_options; i++)
			if(e->motion.x >= menu_x + m->current_menu[i].r.x
			&& e->motion.y >= menu_y + m->current_menu[i].r.y
			&& e->motion.x <  menu_x + m->current_menu[i].r.x + m->current_menu[i].r.w
			&& e->motion.y <  menu_y + m->current_menu[i].r.y + m->current_menu[i].r.h)
			{
				m->option = i;
				draw_menu(m);
				return true;
			}
		return false;
	}
	else if(e->type == SDL_MOUSEBUTTONDOWN)
	{
		int i;
		for(i=0; i < m->n_options; i++)
			if(e->button.x >= menu_x + m->current_menu[i].r.x
			&& e->button.y >= menu_y + m->current_menu[i].r.y
			&& e->button.x <  menu_x + m->current_menu[i].r.x + m->current_menu[i].r.w
			&& e->button.y <  menu_y + m->current_menu[i].r.y + m->current_menu[i].r.h)
			{
				m->option = i;
				if(m->current_menu[m->option].action == UINTEGER)
				{
					if(e->button.button == SDL_BUTTON_LEFT)
						select_option(m, +1);
					else if(e->button.button == SDL_BUTTON_RIGHT)
						select_option(m, -1);
				}
				else
					select_option(m, 0);
				draw_menu(m);
				return true;
			}
		return false;
	}
	else
		return false;
}


// Free allocated memory.
void free_menu(Menu* m)
{
	if(m->sf)
		SDL_FreeSurface(m->sf);
	if(m->tmp_editing)
		free(m->tmp_editing);
	free(m);
}
